using System;
using System.ComponentModel;

namespace Td.Additional.ComponentModel
{
    /// <summary>
    /// Property class.
    /// </summary>
    public class Property : PropertyDescriptor
    {
        #region Fields

        /// <summary>
        /// Property owner.
        /// </summary>
        internal PropertySet owner;
        /// <summary>
        /// Value.
        /// </summary>
        private object value;
        /// <summary>
        /// Category.
        /// </summary>
        private String category;
        /// <summary>
        /// Display name.
        /// </summary>
        private String displayName;
        /// <summary>
        /// Description.
        /// </summary>
        private String description;
        /// <summary>
        /// Property type.
        /// </summary>
        private Type propertyType;
        /// <summary>
        /// Default value.
        /// </summary>
        private object defaultValue;
        /// <summary>
        /// UI type editor.
        /// </summary>
        private object editor;
        /// <summary>
        /// Type converter.
        /// </summary>
        private TypeConverter converter;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Property"/> class.
        /// </summary>
        /// <param name="category">The category.</param>
        /// <param name="name">The name.</param>
        /// <param name="propertyType">Type of the property.</param>
        /// <param name="value">The value.</param>
        public Property(String category, String name, Type propertyType, object value)
            : base(name, new Attribute[0])
        {
            this.category = category;
            this.propertyType = propertyType;
            this.value = value;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Property"/> class.
        /// </summary>
        /// <param name="category">The category.</param>
        /// <param name="name">The name.</param>
        /// <param name="propertyType">Type of the property.</param>
        /// <param name="value">The value.</param>
        /// <param name="description">The description.</param>
        /// <param name="displayName">The display name.</param>
        public Property(String category, String name, Type propertyType, object value, String description, String displayName)
            : this(category, name, propertyType, value)
        {
            this.description = description;
            this.displayName = displayName;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the value.
        /// </summary>
        /// <value>The value.</value>
        public object Value
        {
            get { return GetValue(this); }
            set { SetValue(this, value); }
        }

        /// <summary>
        /// Changed flag
        /// </summary>
        private bool changed;

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="Property"/> is changed.
        /// </summary>
        /// <value><c>true</c> if changed; otherwise, <c>false</c>.</value>
        public bool Changed
        {
            get { return changed; }
            internal set { changed = value; }
        }

        #endregion

        #region PropertyDescriptor members

        /// <summary>
        /// Gets or sets the default value.
        /// </summary>
        /// <value>The default value.</value>
        public object DefaultValue
        {
            get
            {
                return defaultValue;
            }
            set
            {
                if (value.GetType() != propertyType)
                {
                    throw new NotSupportedException("Property value and default value must be of same type");
                }
                defaultValue = value;
            }
        }

        /// <summary>
        /// When overridden in a derived class, gets the type of the component this property is bound to.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// A <see cref="T:System.Type"/> that represents the type of component this property is bound to. When the <see cref="M:System.ComponentModel.PropertyDescriptor.GetValue(System.Object)"/> or <see cref="M:System.ComponentModel.PropertyDescriptor.SetValue(System.Object,System.Object)"/> methods are invoked, the object specified might be an instance of this type.
        /// </returns>
        public override Type ComponentType
        {
            get
            {
                if (owner != null)
                    return owner.GetType();
                return null;
            }
        }

        /// <summary>
        /// Gets the name of the category to which the member belongs, as specified in the <see cref="T:System.ComponentModel.CategoryAttribute"/>.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// The name of the category to which the member belongs. If there is no <see cref="T:System.ComponentModel.CategoryAttribute"/>, the category name is set to the default category, Misc.
        /// </returns>
        /// <PermissionSet>
        /// 	<IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode, ControlEvidence"/>
        /// </PermissionSet>
        public override string Category
        {
            get { return category; }
        }

        /// <summary>
        /// Gets the description of the member, as specified in the <see cref="T:System.ComponentModel.DescriptionAttribute"/>.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// The description of the member. If there is no <see cref="T:System.ComponentModel.DescriptionAttribute"/>, the property value is set to the default, which is an empty string ("").
        /// </returns>
        public override string Description
        {
            get
            {
                if (string.IsNullOrEmpty(description))
                    return string.Empty;
                return description;
            }
        }

        /// <summary>
        /// Gets the name that can be displayed in a window, such as a Properties window.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// The name to display for the member.
        /// </returns>
        public override string DisplayName
        {
            get
            {
                if (string.IsNullOrEmpty(displayName))
                    return Name;
                return displayName;
            }
        }

        /// <summary>
        /// When overridden in a derived class, gets a value indicating whether this property is read-only.
        /// </summary>
        /// <value></value>
        /// <returns>true if the property is read-only; otherwise, false.
        /// </returns>
        public override bool IsReadOnly
        {
            get { return false; }
        }

        /// <summary>
        /// When overridden in a derived class, gets the type of the property.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// A <see cref="T:System.Type"/> that represents the type of the property.
        /// </returns>
        public override Type PropertyType
        {
            get { return propertyType; }
        }

        /// <summary>
        /// Gets the type converter for this property.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// A <see cref="T:System.ComponentModel.TypeConverter"/> that is used to convert the <see cref="T:System.Type"/> of this property.
        /// </returns>
        /// <PermissionSet>
        /// 	<IPermission class="System.Security.Permissions.SecurityPermission, mscorlib, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" Flags="UnmanagedCode"/>
        /// </PermissionSet>
        public override TypeConverter Converter
        {
            get
            {
                if (converter == null)
                {
                    if (propertyType == typeof(PropertySet))
                        converter = new PropertySetConverter();
                    else
                        converter = base.Converter;
                }
                return converter;
            }
        }

        /// <summary>
        /// Gets an editor of the specified type.
        /// </summary>
        /// <param name="editorBaseType">The base type of editor, which is used to differentiate between multiple editors that a property supports.</param>
        /// <returns>
        /// An instance of the requested editor type, or null if an editor cannot be found.
        /// </returns>
        public override object GetEditor(Type editorBaseType)
        {
            if (editor == null)
                editor = base.GetEditor(editorBaseType);
            return editor;
        }

        /// <summary>
        /// When overridden in a derived class, returns whether resetting an object changes its value.
        /// </summary>
        /// <param name="component">The component to test for reset capability.</param>
        /// <returns>
        /// true if resetting the component changes its value; otherwise, false.
        /// </returns>
        public override bool CanResetValue(object component)
        {
            if (defaultValue == null)
                return false;
            return (propertyType == defaultValue.GetType());
        }

        /// <summary>
        /// When overridden in a derived class, gets the current value of the property on a component.
        /// </summary>
        /// <param name="component">The component with the property for which to retrieve the value.</param>
        /// <returns>
        /// The value of a property for a given component.
        /// </returns>
        public override object GetValue(object component)
        {
            return value;
        }

        /// <summary>
        /// When overridden in a derived class, sets the value of the component to a different value.
        /// </summary>
        /// <param name="component">The component with the property value that is to be set.</param>
        /// <param name="value">The new value.</param>
        public override void SetValue(object component, object value)
        {
            if (value.GetType() != this.propertyType)
                throw new ArgumentException(String.Format("value must be from type {0}", this.propertyType.Name), "value");
            bool localChanged = false;
            if (this.value != null)
                localChanged = !this.value.Equals(value);
            else
                localChanged = this.value != value;
            this.value = value;
            if (localChanged)
            {
                changed = true;
                if (owner != null)
                    owner.OnPropertyChanged(owner, new PropertyChangedEventArgs(this.Name));
            }
        }

        /// <summary>
        /// When overridden in a derived class, resets the value for this property of the component to the default value.
        /// </summary>
        /// <param name="component">The component with the property value that is to be reset to the default value.</param>
        public override void ResetValue(object component)
        {
            value = defaultValue;
        }

        /// <summary>
        /// When overridden in a derived class, determines a value indicating whether the value of this property needs to be persisted.
        /// </summary>
        /// <param name="component">The component with the property to be examined for persistence.</param>
        /// <returns>
        /// true if the property should be persisted; otherwise, false.
        /// </returns>
        public override bool ShouldSerializeValue(object component)
        {
            return false;
        }

        #endregion

        #region Property members

        /// <summary>
        /// Adds the attribute, or overwrites an existing.
        /// </summary>
        /// <param name="attribute">The attribute.</param>
        public void AddAttribute(Attribute attribute)
        {
            Attribute[] attributes = this.AttributeArray;
            for (int i = 0; i < attributes.Length; i++)
                if (attributes[i].TypeId == attribute.TypeId)
                {
                    attributes[i] = attribute;
                    return;
                }
            Attribute[] newAttributes = new Attribute[attributes.Length + 1];
            attributes.CopyTo(newAttributes, 0);
            newAttributes[attributes.Length] = attribute;
            this.AttributeArray = newAttributes;
        }

        #endregion

        #region Overriden object members

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            if (value != null)
                if (value is PropertySet)
                    return TypeDescriptor.GetConverter(typeof(PropertySet)).ConvertToInvariantString(value);
                else
                    return value.ToString();
            return String.Empty;
        }

        #endregion
    }
}